﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
#if __MonoCS__
#else
using NetFwTypeLib;
using FWSetupAction;
#endif

namespace VerifyConnection
{
    public partial class Available_Connections_Form : Form
    {

        #region Environment Specific
#if __MonoCS__
        //Need to find mono equivalent for increasing time period.
#else
        [DllImport("WINMM")]
        internal static extern uint timeBeginPeriod(uint period);
        
        Firewall fw = null;
#endif
        #endregion
        #region Consts
        long ns_per_tick = (1000L * 1000L * 1000L) / ( Stopwatch.Frequency );

        string server_host_name = "evanrw.no-ip.biz";
        string client_host_name = "evanrw1.no-ip.biz";

        //These are typically unused ports
        int udp_server_main_port = 9919;
        int udp_server_secondary_port = 9920;

        int udp_client_main_port = 9923;
        int udp_client_secondary_port = 9924;

        int tcp_main_port = 9922;
        int tcp_secondary_port = 9921;

        int http_main_port = 80;
        int http_secondary_port = 8080;

        string http_server_context = "server/";
        #endregion

        bool main_computer = false;
        int delay_time = 1000;

        #region IP Variables
        string my_internal_ip = "0.0.0.0";
        string my_external_ip = "0.0.0.0";
        string target_ip;
        #endregion
        #region Data Logging Variables
        StringBuilder log = new StringBuilder();
        string log_IP_RTT = "";
        string log_UDP_RTT = "";
        string log_TCP_S_RTT = "";
        string log_TCP_C_RTT = "";
        string log_HTTP_S_RTT = "";
        string log_HTTP_C_RTT = "";

        int UDP_Packets_Missed = 0;
        int UDP_Packets_Out_of_Order = 0;
        int UDP_Packets_Recieved = 0;

        int TCP_S_Packets_Missed = 0;
        int TCP_S_Packets_Out_of_Order = 0;
        int TCP_S_Packets_Recieved = 0;

        int TCP_C_Packets_Missed = 0;
        int TCP_C_Packets_Out_of_Order = 0;
        int TCP_C_Packets_Recieved = 0;

        int HTTP_S_Packets_Missed = 0;
        int HTTP_S_Packets_Out_of_Order = 0;
        int HTTP_S_Packets_Recieved = 0;

        int HTTP_C_Packets_Missed = 0;
        int HTTP_C_Packets_Out_of_Order = 0;
        int HTTP_C_Packets_Recieved = 0;
        #endregion
        #region Background Worker Variables
        BackgroundWorker bwlabelIP;
        BackgroundWorker bwPing;
        BackgroundWorker bwUDP_Server;
        BackgroundWorker bwUDP_Client;
        BackgroundWorker bwTCP_Server;
        BackgroundWorker bwTCP_Client;
        BackgroundWorker bwHTTP_Server;
        BackgroundWorker bwHTTP_Client;
        BackgroundWorker bwLog_Data;

        delegate void SetLabel_Ext_IPTextCallback(string text);
        delegate void SetLabel_Loc_IPTextCallback(string text);
        delegate void SetTarget_IPTextCallback(string text);
        delegate void SetUDP_ConnectedTextCallback(string text);
        delegate void SetTCP_S_ConnectedTextCallback(string text);
        delegate void SetTCP_C_ConnectedTextCallback(string text);
        delegate void SetHTTP_S_ConnectedTextCallback(string text);
        delegate void SetHTTP_C_ConnectedTextCallback(string text);

        delegate void SetTarget_IPCheckedCallback(bool value);
        delegate void SetUDP_ConnectedCheckedCallback(bool value);
        delegate void SetTCP_S_ConnectedCheckedCallback(bool value);
        delegate void SetTCP_C_ConnectedCheckedCallback(bool value);
        delegate void SetHTTP_S_ConnectedCheckedCallback(bool value);
        delegate void SetHTTP_C_ConnectedCheckedCallback(bool value);
        #endregion
        #region Main Entry
        public Available_Connections_Form()
        {
            InitializeComponent();

#if __MonoCS__
#else
            //This will enable highest resolution for threading.
            timeBeginPeriod(1);
            
            //Try to open the firewall for http communication
            fw = new Firewall();
            fw.openFirewall();
#endif
            label_My_Ext_IP.Text = "My External IP:";

            Start_Threads();
        }
        #endregion
        #region IP Functions
        public string LocalIPAddress(DoWorkEventArgs e)
        {
            /* Returns the first local IP found               *
             * Improvement would be to add a device to select */
            IPHostEntry host = null;
            string localIP = "";
            while (host == null && !bwlabelIP.CancellationPending)
            {
                host = Dns.GetHostEntry(Dns.GetHostName());
                Thread.Sleep(delay_time);
            }
            if (host != null)
            foreach (IPAddress ip in host.AddressList)
            {
                if (ip.AddressFamily == AddressFamily.InterNetwork)
                {
                    localIP = ip.ToString();
                    break;
                }
            }
            if (bwlabelIP.CancellationPending) e.Cancel = true;
            return localIP;
        }
        public string ExternalIPAddress(DoWorkEventArgs e)
        {
            WebClient webClient = new WebClient();
            string result = null;
            while(result == null && !bwlabelIP.CancellationPending)
            {
                try
                {
                    result = webClient.DownloadString("http://checkip.dyndns.org");
                    string[] ip_garbage = result.Split(':');
                    string[] ip_space = ip_garbage[1].Split('<');
                    string ip = ip_space[0].Trim();
                    return ip;
                }
                catch (Exception)
                {
                    Thread.Sleep(delay_time);
                }
            }
            if (bwlabelIP.CancellationPending) e.Cancel = true;
            return "0.0.0.0";
        }
        public void Update_My_IP(DoWorkEventArgs e)
        {
            lock (my_internal_ip)
            {
                my_internal_ip = LocalIPAddress(e);
            }
            lock (my_external_ip)
            {
                my_external_ip = ExternalIPAddress(e);
            }
            SetLabel_Ext_IPText("My External IP: " + my_external_ip);
            SetLabel_Loc_IPText("My Local IP: " + my_internal_ip);

            string host_name = (main_computer) ? client_host_name : server_host_name;

            var uri = new Uri("https://dynupdate.no-ip.com/nic/update?hostname="+host_name+"&myip="+my_external_ip);
            var request = WebRequest.Create(uri) as HttpWebRequest;
            request.Credentials = new NetworkCredential("evanrw", "maple44");
            request.PreAuthenticate = true;
            WebResponse response = request.GetResponse();
            Stream resp_str = response.GetResponseStream();
            byte[] bytes = new byte[1000];
            resp_str.Read(bytes, 0, 1000);
            string what = Encoding.ASCII.GetString(bytes); 
            //Add error checking here, either see nochng, or good with correct ip, otherwise try again.
        }
        public void Update_Target_IP(DoWorkEventArgs e)
        {
            IPHostEntry hostEntry;

            string host_name = (main_computer) ? server_host_name : client_host_name;

            hostEntry = Dns.GetHostEntry(host_name);

            if (hostEntry.AddressList.Length > 0)
            {
                target_ip = hostEntry.AddressList[0].ToString();
            }
            else
            {
                target_ip = "0.0.0.0";
            }

            SetTarget_IPText( "Target IP: " + target_ip);
        }
        #endregion
        #region Threads
        public void Start_Workers()
        {
            Ping();
            UDP_Server();
            UDP_Client();
            TCP_Server();
            TCP_Client();
            HTTP_Server();
            HTTP_Client();
            Log_Data();
        }
        public void labelIP()
        {
            bwlabelIP = new BackgroundWorker();
            bwlabelIP.WorkerSupportsCancellation = true;
            bwlabelIP.DoWork += new DoWorkEventHandler(labelIP_DoWork);
            bwlabelIP.RunWorkerCompleted += new RunWorkerCompletedEventHandler(labelIP_RunWorkerCompleted);
            bwlabelIP.RunWorkerAsync();
        }
        public void Ping()
        {
            bwPing = new BackgroundWorker();
            bwPing.WorkerSupportsCancellation = true;
            bwPing.DoWork += new DoWorkEventHandler(bwPing_DoWork);
            bwPing.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwPing_RunWorkerCompleted);
            bwPing.RunWorkerAsync();
        }
        public void UDP_Server()
        {
            bwUDP_Server = new BackgroundWorker();
            bwUDP_Server.WorkerSupportsCancellation = true;
            bwUDP_Server.DoWork += new DoWorkEventHandler(bwUDP_Server_DoWork);
            bwUDP_Server.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwUDP_Server_RunWorkerCompleted);
            bwUDP_Server.RunWorkerAsync();
        }
        public void UDP_Client()
        {
            bwUDP_Client = new BackgroundWorker();
            bwUDP_Client.WorkerSupportsCancellation = true;
            bwUDP_Client.DoWork += new DoWorkEventHandler(bwUDP_Client_DoWork);
            bwUDP_Client.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwUDP_Client_RunWorkerCompleted);
            bwUDP_Client.RunWorkerAsync();
        }
        public void TCP_Server()
        {
            bwTCP_Server = new BackgroundWorker();
            bwTCP_Server.WorkerSupportsCancellation = true;
            bwTCP_Server.DoWork += new DoWorkEventHandler(bwTCP_Server_DoWork);
            bwTCP_Server.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwTCP_Server_RunWorkerCompleted);
            bwTCP_Server.RunWorkerAsync();
        }
        public void TCP_Client()
        {
            bwTCP_Client = new BackgroundWorker();
            bwTCP_Client.WorkerSupportsCancellation = true;
            bwTCP_Client.DoWork += new DoWorkEventHandler(bwTCP_Client_DoWork);
            bwTCP_Client.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwTCP_Client_RunWorkerCompleted);
            bwTCP_Client.RunWorkerAsync();
        }
        public void HTTP_Server()
        {
            bwHTTP_Server = new BackgroundWorker();
            bwHTTP_Server.WorkerSupportsCancellation = true;
            bwHTTP_Server.DoWork += new DoWorkEventHandler(bwHTTP_Server_DoWork);
            bwHTTP_Server.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwHTTP_Server_RunWorkerCompleted);
            bwHTTP_Server.RunWorkerAsync();
        }
        public void HTTP_Client()
        {
            bwHTTP_Client = new BackgroundWorker();
            bwHTTP_Client.WorkerSupportsCancellation = true;
            bwHTTP_Client.DoWork += new DoWorkEventHandler(bwHTTP_Client_DoWork);
            bwHTTP_Client.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwHTTP_Client_RunWorkerCompleted);
            bwHTTP_Client.RunWorkerAsync();
        }
        public void Log_Data()
        {
            bwLog_Data = new BackgroundWorker();
            bwLog_Data.WorkerSupportsCancellation = true;
            bwLog_Data.DoWork += new DoWorkEventHandler(bwLog_Data_DoWork);
            bwLog_Data.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bwLog_Data_RunWorkerCompleted);
            bwLog_Data.RunWorkerAsync();
        }
        public void Stop_Workers()
        {
            this.Text = "Verify Connection - Stopping...";

            while (!Workers_Stopped())
            {
                if (bwlabelIP != null) bwlabelIP.CancelAsync();
                if (bwPing != null) bwPing.CancelAsync();
                if (bwUDP_Server != null) bwUDP_Server.CancelAsync();
                if (bwUDP_Client != null) bwUDP_Client.CancelAsync();
                if (bwTCP_Server != null) bwTCP_Server.CancelAsync();
                if (bwTCP_Client != null) bwTCP_Client.CancelAsync();
                if (bwHTTP_Server != null) bwHTTP_Server.CancelAsync();
                if (bwHTTP_Client != null) bwHTTP_Client.CancelAsync();
                if (bwLog_Data != null) bwLog_Data.CancelAsync();
                Application.DoEvents();
                Thread.Yield();
            }

            this.Text = "Verify Connection";
        }
        public bool Workers_Stopped()
        {
            bool val = false;
            if (bwlabelIP != null) val |= bwlabelIP.IsBusy;
            if (bwUDP_Client != null) val |= bwUDP_Client.IsBusy;
            if (bwUDP_Server != null) val |= bwUDP_Server.IsBusy;
            if (bwTCP_Client != null) val |= bwTCP_Client.IsBusy;
            if (bwTCP_Server != null) val |= bwTCP_Server.IsBusy;
            if (bwHTTP_Client != null) val |= bwHTTP_Client.IsBusy;
            if (bwHTTP_Server != null) val |= bwHTTP_Server.IsBusy;
            if (bwPing != null) val |= bwPing.IsBusy;
            if (bwLog_Data != null) val |= bwLog_Data.IsBusy;

            return !val;
        }
        public void Restart_Threads()
        {
            Stop_Workers();

            Start_Threads();
        }
        public void Start_Threads()
        {
            //Setup the IPs first and then on thread completion, start the workers
            labelIP();
        }
        #endregion
        #region DoWork
        public void labelIP_DoWork(object sender, DoWorkEventArgs e)
        {
            SetFormText("Verify Connection - Connecting...");
            Update_My_IP(e);     //Update own IP address (Server location)
            Update_Target_IP(e); //Get the target IP address (Client location)
            SetFormText("Verify Connection");
        }
        public void bwPing_DoWork(object sender, DoWorkEventArgs e)
        {
            // Ping's the target machine.
            Ping pingSender = new Ping();
            PingReply reply = null;
            while (!bwPing.CancellationPending)
            {
                try
                {
                    reply = pingSender.Send(target_ip);
                }
                catch( Exception)
                {
                    Thread.Sleep(delay_time);
                    continue;
                }

                if (reply.Status == IPStatus.Success)
                {
                    //Console.WriteLine("Address: {0}", reply.Address.ToString());
                    //Console.WriteLine("RoundTrip time: {0}", reply.RoundtripTime);
                    //Console.WriteLine("Time to live: {0}", reply.Options.Ttl);
                    //Console.WriteLine("Don't fragment: {0}", reply.Options.DontFragment);
                    //Console.WriteLine("Buffer size: {0}", reply.Buffer.Length);
                    SetTarget_IPText("Target IP: " + target_ip + " - RTT: " + reply.RoundtripTime.ToString() + " ms");
                    log_IP_RTT = reply.RoundtripTime.ToString();
                    SetTarget_IPChecked(true);
                }
                else
                {
                    Console.WriteLine(reply.Status);
                    SetTarget_IPChecked(false);
                }
                Thread.Sleep(delay_time);
            }
            SetTarget_IPChecked(false);
            e.Cancel = true;
        }
        public void bwUDP_Server_DoWork(object sender, DoWorkEventArgs e)
        {
            Stopwatch sw = new Stopwatch();

            int listening_port = (main_computer) ? udp_server_main_port : udp_server_secondary_port;
            int target_port = (main_computer) ? udp_client_secondary_port : udp_client_main_port;

            IPEndPoint MyEndpoint = new IPEndPoint(IPAddress.Any, listening_port);
            IPEndPoint TargetEndPoint = new IPEndPoint(IPAddress.Parse(target_ip), target_port);
            IPEndPoint GenericEndPoint = new IPEndPoint(IPAddress.Any, 0);
            EndPoint GenericClient = (EndPoint)(GenericEndPoint);

            int packet_number_sent = 0;
            int last_packet_number_received = -1;
            int packet_number_received = -1;
            long UDP_RTT = 0;

            Socket WinSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            WinSocket.Bind(MyEndpoint);

            //Timeout so that we can actually receive packets.
            WinSocket.ReceiveTimeout = 2 * delay_time;

            string time_stamp_message = packet_number_sent + "," + UDP_RTT;
            byte[] sendData = new byte[256];
            string[] datas = { "0", "0" }; 
            //Console.WriteLine("Waiting for UDP client...");

            while (!bwUDP_Server.CancellationPending)
            {
                packet_number_sent++;
                time_stamp_message = packet_number_sent + "," + UDP_RTT;
                sendData = Encoding.ASCII.GetBytes(time_stamp_message);
                sw.Reset();
                sw.Start();
                WinSocket.SendTo(sendData, sendData.Length, SocketFlags.None, TargetEndPoint);

                int recv = 0;
                try
                {
                    recv = WinSocket.ReceiveFrom(sendData, ref GenericClient);
                    sw.Stop();
                    //TargetEndPoint = (IPEndPoint)GenericClient;
                    SetUDP_ConnectedChecked(true);
                }
                catch (Exception)
                {
                    //send heartbeat for router
                    packet_number_sent++;
                    time_stamp_message = packet_number_sent + "," + UDP_RTT;
                    sendData = Encoding.ASCII.GetBytes(time_stamp_message);
                    WinSocket.SendTo(sendData, sendData.Length, SocketFlags.None, TargetEndPoint);
                    SetUDP_ConnectedChecked(false);
                    continue;
                }
                datas = Encoding.ASCII.GetString(sendData, 0, recv).Split(',');
                packet_number_received = Int32.Parse(datas[0]);

                UDP_Packets_Recieved++;
                if(last_packet_number_received != -1)
                {
                    if(packet_number_received - 1 > last_packet_number_received)
                    {
                        //We are in a case where we must have missed a packet
                        UDP_Packets_Missed += packet_number_received - last_packet_number_received - 1;
                    }
                    if(packet_number_received < last_packet_number_received)
                    {
                        //We are in a case where the packet recieved is out of order
                        UDP_Packets_Missed--;
                        UDP_Packets_Out_of_Order++;
                        
                        //Ignore this packet, because the RTT will be incorrect.
                        continue;
                    }
                    if (packet_number_received == last_packet_number_received)
                    {
                        //Do nothing, we are ok.
                    }
                }
                last_packet_number_received = packet_number_received;

                long UDP_RTT_Ticks = sw.ElapsedTicks;
                UDP_RTT = UDP_RTT_Ticks * ns_per_tick;
                SetUDP_ConnectedText("UDP Connected - RTT: " + String.Format("{0:N0}", UDP_RTT/1000) + " us");
                log_UDP_RTT = UDP_RTT.ToString();

                Thread.Sleep(delay_time);
            }
            SetUDP_ConnectedChecked(false);
            e.Cancel = true;
            WinSocket.Close();
        }
        public void bwUDP_Client_DoWork(object sender, DoWorkEventArgs e)
        {
            int listening_port = (main_computer) ? udp_client_main_port : udp_client_secondary_port;
            int target_port = (main_computer) ? udp_server_secondary_port : udp_server_main_port;

            IPEndPoint MyEndpoint = new IPEndPoint(IPAddress.Any, listening_port);
            IPEndPoint TargetEndPoint = new IPEndPoint(IPAddress.Parse(target_ip), target_port);
            IPEndPoint GenericEndPoint = new IPEndPoint(IPAddress.Any, 0);
            EndPoint GenericClient = (EndPoint)(GenericEndPoint);

            Socket WinSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            WinSocket.Bind(MyEndpoint);

            if (WinSocket.LocalEndPoint == null) MessageBox.Show("UDP Port did not bind properly. You may need to restart your computer.");

            int packet_number_received = 0;
            string clientSpeaking = "0,0";
            byte[] sendData = Encoding.ASCII.GetBytes(clientSpeaking);

            //Send first UDP packet so NAT router expects new packet.
            WinSocket.SendTo(sendData, sendData.Length, SocketFlags.None, TargetEndPoint);

            //Reset the timeout so that we can actually receive packets.
            WinSocket.ReceiveTimeout = 2 * delay_time;

            byte[] data = new byte[256];
            //Console.WriteLine("Waiting for UDP Server...");
            int recv = 0;
            while (!bwUDP_Client.CancellationPending)
            {
                try
                {
                    recv = WinSocket.ReceiveFrom(data, ref GenericClient);
                }
                catch (Exception)
                {
                    WinSocket.SendTo(sendData, sendData.Length, SocketFlags.None, TargetEndPoint);
                    continue;
                }
                //TargetEndPoint = (IPEndPoint)GenericClient;
               
                //Echo back data asap
                string[] datas = Encoding.ASCII.GetString(data, 0, data.Length).Split(',');
                packet_number_received = Int32.Parse(datas[0]);
                WinSocket.SendTo(data, recv, SocketFlags.None, TargetEndPoint);
                //Could update a UDP_Clien rtt variable.
            }
            e.Cancel = true;
            WinSocket.Close();
        }
        public void bwTCP_Server_DoWork(object sender, DoWorkEventArgs e)
        {
            Stopwatch sw = new Stopwatch();

            int my_port = (main_computer) ? tcp_main_port : tcp_secondary_port;

            IPEndPoint 	MyEndPoint = new IPEndPoint(IPAddress.Any, my_port);
            TcpListener myListener = null;

            int packet_number_sent = 0;
            int last_packet_number_received = -1;
            int packet_number_received = -1;
            long TCP_S_RTT = 0;

            /* Start Listeneting at the specified port */
            myListener = new TcpListener(MyEndPoint);

            while (!bwTCP_Server.CancellationPending)
            {
                try
                {
                    myListener.Stop();
                    myListener.Start();
                    while (!myListener.Pending() && !bwTCP_Server.CancellationPending)
                    {
                        SetTCP_S_ConnectedChecked(false);
                        Thread.Sleep(100);
                    }
                    if (bwTCP_Server.CancellationPending)
                    {
                        e.Cancel = true;
                        myListener.Stop();
                        return;
                    }
                    //Console.WriteLine("The local TCP End point is:" + myListener.LocalEndpoint );
                    //Console.WriteLine("Waiting for a connection...");

                    Socket sock = myListener.AcceptSocket();
                    sock.ReceiveTimeout = 2 * delay_time;
                    //Could add protection here to ensure correct ip...
                    //Console.WriteLine("TCP connection accepted from " + sock.RemoteEndPoint);

                    byte[] received_data = new byte[1000];
                    byte[] send_data = new byte[1000];

                    string time_tuple;

                    while (!bwTCP_Server.CancellationPending && sock.Connected)
                    {
                        SetTCP_S_ConnectedChecked(true);

                        send_data = Encoding.ASCII.GetBytes((++packet_number_sent).ToString() + "," + TCP_S_RTT);

                        sw.Reset();
                        sw.Start();

                        sock.Send(send_data);
                        int recv_count = sock.Receive(received_data);

                        sw.Stop();

                        //Parse packet number, and client rtt from data.
                        time_tuple = Encoding.ASCII.GetString(received_data, 0, recv_count);
                        string[] split_tuple = time_tuple.Split(',');
                        packet_number_received = Int32.Parse(split_tuple[0]);

                        TCP_S_Packets_Recieved++;
                        if (last_packet_number_received != -1)
                        {
                            if (packet_number_received - 1 > last_packet_number_received)
                            {
                                //We are in a case where we must have missed a packet
                                TCP_S_Packets_Missed += packet_number_received - last_packet_number_received - 1;
                            }
                            if (packet_number_received < last_packet_number_received)
                            {
                                //We are in a case where the packet recieved is out of order
                                TCP_S_Packets_Missed--;
                                TCP_S_Packets_Out_of_Order++;

                                //Ignore this packet, because the RTT will be incorrect.
                                Thread.Sleep(delay_time);
                                continue;
                            }
                            if(packet_number_received == last_packet_number_received)
                            {
                                //Do nothing, we are ok.
                            }
                        }
                        last_packet_number_received = packet_number_received;

                        long TCP_S_RTT_Ticks = sw.ElapsedTicks;
                        TCP_S_RTT = TCP_S_RTT_Ticks * ns_per_tick;
                        SetTCP_S_ConnectedText("TCP-S Connected - RTT: " + String.Format("{0:N0}", TCP_S_RTT / 1000) + " us");
                        log_TCP_S_RTT = TCP_S_RTT.ToString();

                        Thread.Sleep(delay_time);
                    }
                    SetTCP_S_ConnectedChecked(false);
                }
                catch (Exception)
                {
                    SetTCP_S_ConnectedChecked(false);
                }

            }
        }
        public void bwTCP_Client_DoWork(object sender, DoWorkEventArgs e)
        {
            int target_port = (main_computer) ? tcp_secondary_port : tcp_main_port;
            int my_port = (main_computer) ? tcp_main_port : tcp_secondary_port;

            IPEndPoint TargetEndPoint = new IPEndPoint(IPAddress.Parse(target_ip), target_port);

            byte[] byteBuffer = new byte[1000];

            string time_tuple;
            string[] split_tuple = new string[2];
            split_tuple[0] = "0";
            split_tuple[1] = "0";
            int bufferSize;
            //We need to use a socket, so we can timout properly for the thread.
            Socket WinSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            WinSocket.ReceiveTimeout = 2 * delay_time;
            WinSocket.SendTimeout = 2 * delay_time;

            int last_packet_number_received = -1;
            int packet_number_received = -1;

            try
            {
                IAsyncResult connect_result = WinSocket.BeginConnect(TargetEndPoint.Address, TargetEndPoint.Port, null, null);
                bool connectionSuccess = connect_result.AsyncWaitHandle.WaitOne(delay_time, true);
                if (!connectionSuccess)
                {
                    if (bwTCP_Client.CancellationPending)
                    {
                        e.Cancel = true;
                    }
                    SetTCP_C_ConnectedChecked(false);
                    if (WinSocket.Connected) WinSocket.Disconnect(false);
                    WinSocket.Close();
                    return;
                }
                while (!bwTCP_Client.CancellationPending && WinSocket.Connected)
                {
                    bufferSize = WinSocket.Receive(byteBuffer, 0, byteBuffer.Length, SocketFlags.None);
                    SetTCP_C_ConnectedChecked(true);

                    //Parse old time from data
                    time_tuple = Encoding.ASCII.GetString(byteBuffer, 0, bufferSize);
                    split_tuple = time_tuple.Split(',');

                    packet_number_received = Int32.Parse(split_tuple[0]);

                    TCP_C_Packets_Recieved++;
                    if (last_packet_number_received != -1)
                    {
                        if (packet_number_received - 1 > last_packet_number_received)
                        {
                            //We are in a case where we must have missed a packet
                            TCP_C_Packets_Missed += packet_number_received - last_packet_number_received - 1;
                        }
                        if (packet_number_received < last_packet_number_received)
                        {
                            //We are in a case where the packet recieved is out of order
                            TCP_C_Packets_Missed--;
                            TCP_C_Packets_Out_of_Order++;

                            //Ignore this packet, because the RTT will be incorrect.
                            Thread.Sleep(delay_time);
                            continue;
                        }
                        if (packet_number_received == last_packet_number_received)
                        {
                            //Do nothing, we are ok.
                        }
                    }
                    last_packet_number_received = packet_number_received;

                    long TCP_C_RTT = Int64.Parse(split_tuple[1]);
                    SetTCP_C_ConnectedText("TCP-C Connected - RTT: " + String.Format("{0:N0}", TCP_C_RTT / 1000) + " us");
                    log_TCP_C_RTT = TCP_C_RTT.ToString();

                    WinSocket.Send(byteBuffer, 0, bufferSize, SocketFlags.None);
                }
                SetTCP_C_ConnectedChecked(false);
                if (WinSocket.Connected) WinSocket.Disconnect(false);
                WinSocket.Close();
            }
            catch (Exception exp)
            {
                SetTCP_C_ConnectedChecked(false);
                Console.WriteLine(exp.Message.ToString());
                if (WinSocket.Connected) WinSocket.Disconnect(false);
                WinSocket.Close();
            }
            if(bwTCP_Client.CancellationPending) e.Cancel = true;
        }
        public void bwHTTP_Server_DoWork(object sender, DoWorkEventArgs e)
        {
            string my_address = (main_computer) ? client_host_name : server_host_name;

            int target_port = (main_computer) ? http_main_port: http_secondary_port;

            HttpListener http_listener = new HttpListener();
            http_listener.Prefixes.Add("http://" + my_address + ":" + target_port + "/" + http_server_context);
            http_listener.Start();
            string input_buf = "0,0";

            int last_packet_number_received = -1;
            int packet_number_received = -1;

            while (http_listener.IsListening && !bwHTTP_Server.CancellationPending)
            {
                HttpListenerContext ctx = http_listener.GetContext();

                string rstr = string.Format("<HTML><BODY>{0}</BODY></HTML>", input_buf);
                byte[] buf = Encoding.UTF8.GetBytes(rstr);
                ctx.Response.ContentLength64 = buf.Length;
                ctx.Response.OutputStream.Write(buf, 0, buf.Length);

                SetHTTP_S_ConnectedChecked(true);

                string[] strings = ctx.Request.Url.ToString().Split('?');
                string[] datas = strings[1].Split(',');

                HTTP_S_Packets_Recieved++;
                if (last_packet_number_received != -1)
                {
                    if (packet_number_received - 1 > last_packet_number_received)
                    {
                        //We are in a case where we must have missed a packet
                        HTTP_S_Packets_Missed += packet_number_received - last_packet_number_received - 1;
                    }
                    if (packet_number_received < last_packet_number_received)
                    {
                        //We are in a case where the packet recieved is out of order
                        HTTP_S_Packets_Missed--;
                        HTTP_S_Packets_Out_of_Order++;

                        //Ignore this packet, because the RTT will be incorrect.
                        continue;
                    }
                    if (packet_number_received == last_packet_number_received)
                    {
                        //Do nothing, we are ok.
                    }
                }
                last_packet_number_received = packet_number_received;
                
                input_buf = strings[1];
                long HTTP_RTT = Int64.Parse(datas[1]);
                SetHTTP_S_ConnectedText("HTTP-S Connected - RTT: " + String.Format("{0:N0}", HTTP_RTT/1000) + " us");
                log_HTTP_S_RTT = HTTP_RTT.ToString();
            }
            if (bwHTTP_Server.CancellationPending) e.Cancel = true;
            SetHTTP_S_ConnectedChecked(false);
            http_listener.Close();
        }
        public void bwHTTP_Client_DoWork(object sender, DoWorkEventArgs e)
        {
            Stopwatch sw = new Stopwatch();

            string target_address = (main_computer) ? server_host_name : client_host_name;

            int target_port = (main_computer) ? http_secondary_port : http_main_port;


            int packet_number_sent = 0;
            int last_packet_number_received = -1;
            int packet_number_received = -1;
            long HTTP_RTT = 0;

            while(!bwHTTP_Client.CancellationPending)
            {
                Stream data = null;
                //Inject delay time so server isn't spammed.
                Thread.Sleep(delay_time);
                sw.Reset();
                sw.Start();
                try
                {
                    WebRequest request = WebRequest.Create("http://" + target_address + ":" + target_port.ToString() + "/" + http_server_context + "?" + (++packet_number_sent).ToString() + "," + HTTP_RTT.ToString());
                    request.Timeout = (delay_time);
                    WebResponse response = request.GetResponse();
                    data = response.GetResponseStream();
                }
                catch(Exception)
                {
                    SetHTTP_C_ConnectedChecked(false);
                    continue;
                }
                StreamReader reader = new StreamReader(data);
                string s = reader.ReadToEnd();
                sw.Stop();
                if (s.Length > 0)
                {
                    SetHTTP_C_ConnectedChecked(true);
                }

                string[] gt_parsed = s.Split('>');
                string[] lt_parsed = gt_parsed[2].Split('<');
                string[] datas = lt_parsed[0].Split(',');

                packet_number_received = Int32.Parse(datas[0]);

                HTTP_C_Packets_Recieved++;
                if (last_packet_number_received != -1)
                {
                    if (packet_number_received - 1 > last_packet_number_received)
                    {
                        //We are in a case where we must have missed a packet
                        HTTP_C_Packets_Missed += packet_number_received - last_packet_number_received - 1;
                    }
                    if (packet_number_received < last_packet_number_received)
                    {
                        //We are in a case where the packet recieved is out of order
                        HTTP_C_Packets_Missed--;
                        HTTP_C_Packets_Out_of_Order++;

                        //Ignore this packet, because the RTT will be incorrect.
                        continue;
                    }
                    if (packet_number_received == last_packet_number_received)
                    {
                        //Do nothing, we are ok.
                    }
                }
                last_packet_number_received = packet_number_received;

                long HTTP_RTT_Ticks = sw.ElapsedTicks;
                HTTP_RTT = HTTP_RTT_Ticks * ns_per_tick;
                SetHTTP_C_ConnectedText("HTTP-C Connected - RTT: " + String.Format("{0:N0}", HTTP_RTT/1000) + " us");
                log_HTTP_S_RTT = HTTP_RTT.ToString();

                data.Close();
                reader.Close();
            }
            SetHTTP_C_ConnectedChecked(false);
            e.Cancel = true;
        }
        public void bwLog_Data_DoWork(object sender, DoWorkEventArgs e)
        {
            log.Clear();
            log.Append( "Date:," + DateTime.Now.ToString() + ",Delay time:," + delay_time.ToString() + 
                        ",External IP:," + my_external_ip + ",Internal IP:," + my_internal_ip + "\r\n" +
                        "IMCP_RTT,UDP_RTT,TCP_S_RTT,TCP_C_RTT,HTTP_S_RTT,HTTP_C_RTT\r\n");

            while(!bwLog_Data.CancellationPending)
            {
                lock (log_IP_RTT) log.Append(log_IP_RTT + ",");
                lock (log_UDP_RTT) log.Append(log_UDP_RTT + ",");
                lock (log_TCP_S_RTT) log.Append(log_TCP_S_RTT + ",");
                lock (log_TCP_C_RTT) log.Append(log_TCP_C_RTT + ",");
                lock (log_HTTP_S_RTT) log.Append(log_HTTP_S_RTT + ",");
                lock (log_HTTP_C_RTT) log.Append(log_HTTP_C_RTT + ",");
                log.Append("\r\n");
                Thread.Sleep( 2 * delay_time);
            }

            log.Append("\r\nProtocol,Packets Missed,Out of Order,Recieved,Percent Good Packets\r\n");
            log.Append("UDP,"+UDP_Packets_Missed+","+UDP_Packets_Out_of_Order+","+UDP_Packets_Recieved+","+(1 - (UDP_Packets_Missed + UDP_Packets_Out_of_Order)/((double)UDP_Packets_Recieved))+"\r\n");
            log.Append("TCP_S,"+TCP_S_Packets_Missed+","+TCP_S_Packets_Out_of_Order+","+TCP_S_Packets_Recieved+","+(1 - (TCP_S_Packets_Missed + TCP_S_Packets_Out_of_Order)/((double)TCP_S_Packets_Recieved))+"\r\n");
            log.Append("TCP_C,"+TCP_C_Packets_Missed+","+TCP_C_Packets_Out_of_Order+","+TCP_C_Packets_Recieved+","+(1 - (TCP_C_Packets_Missed + TCP_C_Packets_Out_of_Order)/((double)TCP_C_Packets_Recieved))+"\r\n");
            log.Append("HTTP_S,"+HTTP_S_Packets_Missed+","+HTTP_S_Packets_Out_of_Order+","+HTTP_S_Packets_Recieved+","+(1 - (HTTP_S_Packets_Missed + HTTP_S_Packets_Out_of_Order)/((double)HTTP_S_Packets_Recieved))+"\r\n");
            log.Append("HTTP_C,"+HTTP_C_Packets_Missed+","+HTTP_C_Packets_Out_of_Order+","+HTTP_C_Packets_Recieved+","+(1 - (HTTP_C_Packets_Missed + HTTP_C_Packets_Out_of_Order)/((double)HTTP_C_Packets_Recieved))+"\r\n");
            log.Append("\r\n");
            e.Cancel = true;
        }
        #endregion
        #region RunWorkerCompleted
        public void labelIP_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled != true) && (e.Error == null) && !bwlabelIP.IsBusy)
            {
                Start_Workers();
            }
        }
        public void bwPing_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled != true) && (e.Error == null) && !bwPing.IsBusy)
            {
                //bwPing.RunWorkerAsync();
            }
        }
        public void bwUDP_Server_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled != true) && (e.Error == null) && !bwUDP_Server.IsBusy)
            {
                //bwUDP_Server.RunWorkerAsync();
            }
        }
        public void bwUDP_Client_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled != true) && (e.Error == null) && !bwUDP_Client.IsBusy)
            {
                //bwUDP_Client.RunWorkerAsync();
            }
        }
        public void bwTCP_Server_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled != true) && (e.Error == null) && !bwTCP_Server.IsBusy)
            {
                //bwTCP_Server.RunWorkerAsync();
            }
        }
        public void bwTCP_Client_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled != true) && (e.Error == null) && !bwTCP_Client.IsBusy)
            {
                bwTCP_Client.RunWorkerAsync();
            }
        }
        public void bwHTTP_Server_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled != true) && (e.Error == null) && !bwHTTP_Server.IsBusy)
            {
                //bwHTTP_Server.RunWorkerAsync();
            }
        }
        public void bwHTTP_Client_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled != true) && (e.Error == null) && !bwHTTP_Client.IsBusy)
            {
                //bwHTTP_Client.RunWorkerAsync();
            }
        }
        public void bwLog_Data_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if ((e.Cancelled != true) && (e.Error == null) && !bwLog_Data.IsBusy)
            {

            }
        }
        #endregion
        #region Thread Safe Text Setting
        private void SetFormText(string text)
        {
            if (!this.IsHandleCreated)
            {
                this.CreateHandle();
            }

            if (this.InvokeRequired)
            {
                SetTarget_IPTextCallback d = new SetTarget_IPTextCallback(SetFormText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.Text = text;
            }

        }
        private void SetLabel_Ext_IPText(string text)
        {
            if (this.label_My_Ext_IP.InvokeRequired)
            {
                SetTarget_IPTextCallback d = new SetTarget_IPTextCallback(SetLabel_Ext_IPText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.label_My_Ext_IP.Text = text;
            }
        }
        private void SetLabel_Loc_IPText(string text)
        {
            if (this.label_My_Loc_IP.InvokeRequired)
            {
                SetLabel_Loc_IPTextCallback d = new SetLabel_Loc_IPTextCallback(SetLabel_Loc_IPText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.label_My_Loc_IP.Text = text;
            }
        }
        private void SetTarget_IPText(string text)
        {
            if (this.cbTarget_IP.InvokeRequired)
            {
                SetTarget_IPTextCallback d = new SetTarget_IPTextCallback(SetTarget_IPText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.cbTarget_IP.Text = text;
            }
        }
        private void SetUDP_ConnectedText(string text)
        {
            if (this.cbUDP_Connected.InvokeRequired)
            {
                SetUDP_ConnectedTextCallback d = new SetUDP_ConnectedTextCallback(SetUDP_ConnectedText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.cbUDP_Connected.Text = text;
            }
        }
        private void SetTCP_S_ConnectedText(string text)
        {
            if (this.cbTCP_S_Connected.InvokeRequired)
            {
                SetTCP_S_ConnectedTextCallback d = new SetTCP_S_ConnectedTextCallback(SetTCP_S_ConnectedText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.cbTCP_S_Connected.Text = text;
            }
        }
        private void SetTCP_C_ConnectedText(string text)
        {
            if (this.cbTCP_C_Connected.InvokeRequired)
            {
                SetTCP_C_ConnectedTextCallback d = new SetTCP_C_ConnectedTextCallback(SetTCP_C_ConnectedText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.cbTCP_C_Connected.Text = text;
            }
        }
        private void SetHTTP_S_ConnectedText(string text)
        {
            if (this.cbHTTP_S_Connected.InvokeRequired)
            {
                SetHTTP_S_ConnectedTextCallback d = new SetHTTP_S_ConnectedTextCallback(SetHTTP_S_ConnectedText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.cbHTTP_S_Connected.Text = text;
            }
        }
        private void SetHTTP_C_ConnectedText(string text)
        {
            if (this.cbHTTP_C_Connected.InvokeRequired)
            {
                SetHTTP_C_ConnectedTextCallback d = new SetHTTP_C_ConnectedTextCallback(SetHTTP_C_ConnectedText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                this.cbHTTP_C_Connected.Text = text;
            }
        }
        #endregion
        #region Thread Safe Check Box Setting
        private void SetTarget_IPChecked(bool value)
        {
            if (this.cbTarget_IP.InvokeRequired)
            {
                SetTarget_IPCheckedCallback d = new SetTarget_IPCheckedCallback(SetTarget_IPChecked);
                this.Invoke(d, new object[] { value });
            }
            else
            {
                this.cbTarget_IP.Checked = value;
            }
        }
        private void SetUDP_ConnectedChecked(bool value)
        {
            if (this.cbUDP_Connected.InvokeRequired)
            {
                SetUDP_ConnectedCheckedCallback d = new SetUDP_ConnectedCheckedCallback(SetUDP_ConnectedChecked);
                this.Invoke(d, new object[] { value });
            }
            else
            {
                this.cbUDP_Connected.Checked = value;
            }
        }
        private void SetTCP_S_ConnectedChecked(bool value)
        {
            if (this.cbTCP_S_Connected.InvokeRequired)
            {
                SetTCP_S_ConnectedCheckedCallback d = new SetTCP_S_ConnectedCheckedCallback(SetTCP_S_ConnectedChecked);
                this.Invoke(d, new object[] { value });
            }
            else
            {
                this.cbTCP_S_Connected.Checked = value;
            }
        }
        private void SetTCP_C_ConnectedChecked(bool value)
        {
            if (this.cbTCP_C_Connected.InvokeRequired)
            {
                SetTCP_C_ConnectedCheckedCallback d = new SetTCP_C_ConnectedCheckedCallback(SetTCP_C_ConnectedChecked);
                this.Invoke(d, new object[] { value });
            }
            else
            {
                this.cbTCP_C_Connected.Checked = value;
            }
        }
        private void SetHTTP_S_ConnectedChecked(bool value)
        {
            if (this.cbHTTP_S_Connected.InvokeRequired)
            {
                SetHTTP_S_ConnectedCheckedCallback d = new SetHTTP_S_ConnectedCheckedCallback(SetHTTP_S_ConnectedChecked);
                this.Invoke(d, new object[] { value });
            }
            else
            {
                this.cbHTTP_S_Connected.Checked = value;
            }
        }
        private void SetHTTP_C_ConnectedChecked(bool value)
        {
            if (this.cbHTTP_C_Connected.InvokeRequired)
            {
                SetHTTP_C_ConnectedCheckedCallback d = new SetHTTP_C_ConnectedCheckedCallback(SetHTTP_C_ConnectedChecked);
                this.Invoke(d, new object[] { value });
            }
            else
            {
                this.cbHTTP_C_Connected.Checked = value;
            }
        }
        #endregion
        #region Meun Items
        private void targetURLToolStripMenuItem_Click(object sender, EventArgs e)
        {
            changeToServerModeToolStripMenuItem.Enabled = false;
            targetURLToolStripMenuItem.Enabled = false;
            Restart_Threads();
            changeToServerModeToolStripMenuItem.Enabled = true;
            targetURLToolStripMenuItem.Enabled = true;
        }

        private void changeToServerModeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            changeToServerModeToolStripMenuItem.Enabled = false;
            targetURLToolStripMenuItem.Enabled = false;
            Stop_Workers();

            if (main_computer)
            {
                main_computer = false;

                changeToServerModeToolStripMenuItem.Text = "Change to Client Mode";
            }
            else
            {
                main_computer = true;

                changeToServerModeToolStripMenuItem.Text = "Change to Server Mode";
            }

            Start_Threads();
            changeToServerModeToolStripMenuItem.Enabled = true;
            targetURLToolStripMenuItem.Enabled = true;
        }

        private void exportCSVLogToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveCSV();
        }
        #endregion
        #region CSV
        public void SaveCSV()
        {
            bwLog_Data.CancelAsync();
            DialogResult result = saveFileDialog1.ShowDialog();
            if (result.Equals(DialogResult.OK))
            {
                System.IO.Stream fileStream = saveFileDialog1.OpenFile();
                System.IO.StreamWriter sw = new System.IO.StreamWriter(fileStream);
                sw.WriteLine(log.ToString());
                sw.Flush();
                sw.Close();
            }
            bwLog_Data.RunWorkerAsync();
        }
        #endregion

        private void Available_Connections_Form_FormClosed(object sender, FormClosedEventArgs e)
        {
#if __MonoCS__
#else
            if(fw != null) fw.closeFirewall();
#endif
        }
    }
}
